########################################################################
#  Searx-Qt - Lightweight desktop application for Searx.
#  Copyright (C) 2020-2022  CYBERDEViL
#
#  This file is part of Searx-Qt.
#
#  Searx-Qt is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  Searx-Qt is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
########################################################################


import datetime


class VersionType:
  Unknown  = 0
  Semantic = 1
  Date     = 2

class VersionFlags:
  No      = 0  # No flags
  Git     = 1  # Git version (short commit hash present)
  # SearXNG: +dirty Uncommited changes to git (except searx/settings.yml)
  # See: https://github.com/searxng/searxng/blob/master/searx/version.py
  Dirty   = 2
  Extra   = 4  # SearXNG: TODO idk what this is, extra files?
  # SearX: Unknown git commit
  # See: Dockerfile:59:ARG VERSION_GITCOMMIT=unknown
  # See: https://github.com/searx/searx/blob/master/Dockerfile#L65C11-L65C34
  Unknown = 8
  Invalid = 128

VERSION_TYPE_STR = [
  "Unknown",
  "Semantic",
  "Date"
]

VERSION_FLAG_STR = {
  0: "None",
  1: "Git",
  2: "Dirty",
  4: "Extra",
  8: "Unknown",
  128: "Invalid"
}


class InstanceVersion:
  def __init__(self, versionString=""):
    self.set(versionString)

  def __str__(self):
    return self.__str

  def __ge__(self, other):
    # Check if Major/Year version is greater
    otherParts = other.parts()
    if self.__parts[0] < otherParts[0]:
        return False
    elif self.__parts[0] == otherParts[0]:
        # Check if Minor/Month version is greater
        if self.__parts[1] < otherParts[1]:
            return False
        elif self.__parts[1] == otherParts[1]:
            # Check if Patch/Day version is greater
            if self.__parts[2] < otherParts[2]:
                return False

    return True

  def __lt__(self, other):
    return not (self >= other)

  def type(self):
    return self.__type

  def flags(self):
    return self.__flags

  def isValid(self):
    return self.__valid

  def error(self):
    return self.__error

  def parts(self):
    # Tuple with three ints (semantic version or date)
    return self.__parts

  def partsString(self):
    return f"{self.__parts[0]}.{self.__parts[1]}.{self.__parts[2]}"

  def commitHash(self):
    return self.__commit

  def set(self, versionString):
    # SearX examples:
    # --------------
    # 1.0.0
    # 1.0.0-unknown
    # 1.1.0-69-75b859d2
    #
    # SearXNG examples:
    # ----------------
    # 1.0.0
    # 2023.10.22
    # 2023.10.22+526d5c7b3
    # 2023.10.22+526d5c7b3+dirty
    # 2023.10.22+526d5c7b3+extra
    # 2023.10.22+526d5c7b3+dirty+extra
    # 2023.10.22+fd1422a6 (1dcc5768)

    # Set/reset values
    self.__str    = str(versionString)
    self.__type   = VersionType.Unknown
    self.__flags  = VersionFlags.No
    self.__parts  = (0,0,0)
    self.__commit = ""
    self.__otherCommit = ""
    self.__valid  = False
    self.__error  = ""

    if not versionString:
      return

    separator = ""

    if "+" in versionString:  # SearXNG version with additions
        elements  = versionString.split("+")
        separator = "+"
    elif "-" in versionString:  # SearX version with additions
        elements  = versionString.split("-")
        separator = "-"
    else:
        elements  = [versionString]

    vElements = elements[0].split(".")  # Should be a tuple with 3 ints

    # Verify that 'vElements' its length is 3 and that all values are digits
    if len(vElements) != 3:
      self.__error = "not 3 elements seperated by '.'"
      return
    for vElem in vElements:
      if not vElem.isdigit():
        self.__error = "non digit value in version"
        return

    # Initial date version type detected
    if int(vElements[0]) > 2000:
      # Verify date version
      vYear    = int(vElements[0])
      vMonth   = int(vElements[1])
      vDay     = int(vElements[2])
      thisYear = datetime.date.today().year

      # Verify that the year is either this year or of a maximum of 3 years
      # ago.
      if vYear > thisYear or vYear < (thisYear - 3):
        self.__error = "year of the date is out of range"
        return

      # Verify that the month is in range 1-12
      if vMonth < 1 or vMonth > 12:
        self.__error = "month of the date is out of range [1-12]"
        return

      # Verify that the day is in range 1-31
      if vDay < 1 or vDay > 31:
        self.__error = "day of the date is out of range [1-31]"
        return

      self.__type  = VersionType.Date
      self.__parts = (vYear, vMonth, vDay)

    # Semantic version
    else:
      vMajor       = int(vElements[0])
      vMinor       = int(vElements[1])
      vPatch       = int(vElements[2])
      self.__type  = VersionType.Semantic
      self.__parts = (vMajor, vMinor, vPatch)

    # "+" or "-" found in the version string, probably a git short hash is
    # behind the first separator, we need to check that it is actually a
    # short commit hash.
    if len(elements) > 1:
      # SearX:
      # "unknown" addition, example: "1.0.0-unknown"
      if elements[1] == "unknown":
        if len(elements) > 2:
            self.__error = "did not expect anything after '-unknown'"
            return
        self.__flags += VersionFlags.Unknown
        self.__valid = True
        return

      # Here we assume the first element is a short commit hash
      vHash = elements[1]

      # SearXNG:
      # It could be the case that there is a space and then another hash
      # between parentheses, example: "2023.10.5+fd1422a6 (1dcc5768)". IDK
      # what that hash represents, so TODO.
      if " " in vHash:
        spaceSepElems = vHash.split(" ")
        # We expect two elements after split by space in this case.
        if len(spaceSepElems) != 2:
          self.__error = "unknown format (1)"
          return

        otherHash = spaceSepElems[1]

        # Check that the length of the other hash is in expected range.
        if len(otherHash) < 10 or len(otherHash) > 12:
          self.__error = "otherHash check: invalid test string length"
          return

        # Sanity check that the other hash is between parenthesis.
        if otherHash[0] != "(" or otherHash[-1] != ")":
          self.__error = ("otherHash check: does not start with '(' or does not"
                          "end with ')'")
          return

        # Remove the parenthesis
        otherHash = otherHash[1:len(otherHash)-2]

        # Check that the other hash is a hexadecimal value
        try:
          int(otherHash, 16)
        except ValueError:
          self.__error = "invalid other short commit hash, none hex characters in the string"
          return

        # We survived the above checks (this is a "+fd1422a6 (1dcc5768)" case).
        vHash = spaceSepElems[0]
        self.__otherCommit = otherHash[1:len(otherHash)-2]

      # SearX: git rev? ("1.1.0-69-75b859d2")
      if separator == "-" and len(elements) == 3:
        if not elements[1].isnumeric():
          self.__error = "unknown format (2)"
          return
        try:
          int(elements[2], 16)
        except ValueError:
          self.__error = "unknown format (3)"
          return
        self.__flags += VersionFlags.Git
        self.__commit = elements[2]
        self.__valid = True
        return

      # Only SearXNG versions remain..:

      # Check that each character of the short commit hash is in the hex
      # range.
      try:
        int(vHash, 16)
      except ValueError:
        self.__error = "invalid short commit hash, none hex characters in the string"
        return

      self.__flags += VersionFlags.Git
      self.__commit = vHash

      # SearXNG: Check flags
      if len(elements) >= 3:
        for i in range(2, len(elements)):
          if elements[i] == "dirty":
            self.__flags += VersionFlags.Dirty
          elif elements[i] == "extra":
            self.__flags += VersionFlags.Extra
          else:
            self.__error = f"unknown addition '{elements[i]}')"
            return

    self.__valid = True
